{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：生成器和迭代器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "迭代是处理数据的重要环节，基本上对大量数据的处理上，我们都需要对数据进行迭代操作，如何在节省内存开销且高效地去对数据迭代，这就是生成器存在的意义。对于生成器和迭代器的用法，面试者需要了如指掌。\n",
    "\n",
    "答案\n",
    "迭代器实现了__next__方法，返回序列中的下一个元素；如果没有元素了，那么抛出 StopIteration 异常。另外迭代器实现了__iter__方法，用于返回迭代器本身。\n",
    "\n",
    "所有生成器都是迭代器，会生成传给 yield 关键字的表达式的值。调用生成器函数返回生成器，而生成器可以产出值。同样，当没有值可以产出时，会抛出 StopIteration 异常。\n",
    "\n",
    "拓展\n",
    "关于生成器和迭代器的基本用法，大家可以回顾一下第 5 小节的 “python 中 yield 的用法”。今天介绍的是 yield 和 iter 的延伸用法。\n",
    "\n",
    "yield from iterator\n",
    "\n",
    "这个语法多用于嵌套生成器。它有两种用法：一，从生成器中读取数据；二，创建通道，把内层生成器直接与外层生成器联系起来，把生成器当协程使用。\n",
    "\n",
    "先看看 yield from 的基本用法，通过将生成器拆分为多个生成器，您可以轻松地对其进行重构。\n",
    "\n",
    "版本一：不使用 yield from\n",
    "\n",
    "def generator2():\n",
    "    for i in range(10):\n",
    "        yield i\n",
    "\n",
    "def generator3():\n",
    "    for j in range(10, 20):\n",
    "        yield j\n",
    "\n",
    "def generator():\n",
    "    for i in generator2():\n",
    "        yield i\n",
    "    for j in generator3():\n",
    "        yield j\n",
    "版本二：我们用 yield from 来改写上面的 generator():\n",
    "\n",
    "def generator():\n",
    "    yield from generator2() # 对for循环进行重构，这个版本使用`yield from`减少了手动循环\n",
    "    yield from generator3()\n",
    "Tips: 如果你想在生成器中调用其他生成器作为子例程，yield from 这个时候非常有用。\n",
    "如果你不使用它的话，那么就必须写额外的 for 循环了。\n",
    "\n",
    "当生成器有了 send 方法\n",
    "\n",
    "生成器对象有几个重要的 API，send，throw，close，这些在协程中\n",
    "\n",
    "先从一个简单的协程例子讲起：\n",
    "\n",
    "def accumulator():\n",
    "    total = 0\n",
    "    while True:\n",
    "        print(\"Total is \", total)\n",
    "\n",
    "        # 生成器的调用方可以使用`.send(...)`方法发送数据，发送的数据会成为`yield`表达式的值。\n",
    "        # 所以，这里的input是从send传进来的值，而非yield产出的值\n",
    "        # yield total相当于函数return total， 只不过这个函数不是真正的return，而是在这个位置挂起等待下一次调用。\n",
    "        input = yield total\n",
    "        print(\"Send: \", input)\n",
    "        total += input\n",
    "        print(\"Adding %d ... => Total is %d\"%(input, total))\n",
    "\n",
    ">>> gen = accumulator()\n",
    ">>> next(gen)  # 激活协程，计算停在`yield`，也可以使用gen.send(None)激活协程\n",
    "Total is 0\n",
    "0\n",
    ">>> gen.send(1) # 从刚刚停留的位置开始，传入数据开始计算\n",
    "Send:  1\n",
    "Adding 1 ... => Total is 1\n",
    "Total is  1    # 运行又停在`yield`处，等待传入新数值\n",
    "0 # 这是yield total的产生结果，就相当于函数最后的return，比方说，你去调用一个用return返回的函数，最后都会输出return的结果。\n",
    ">>> gen.send(12)\n",
    "Send:  12\n",
    "Adding 12 ... => Total is 13\n",
    "Total is  13\n",
    "13\n",
    ">>> try:\n",
    "...    gen.throw(ValueError) # 或调用gen.close()终止协程\n",
    "...except ValueError:\n",
    "...    pass\n",
    ">>> gen.send(12)\n",
    "StopIteration\n",
    "从上面的代码段，我可以基本了解了使用协程的基本步骤为：\n",
    "\n",
    "创建协程对象\n",
    "调用 next 函数，激活协程\n",
    "调用.send(...) 方法，推动协程执行并产出\n",
    "调用方可以通过.close(...) 或.throw() 方法终止协程，如果继续推进协程会抛出 StopIteration。\n",
    "Tips: 生成器的调用方可以使用 .send(...) 方法发送数据，它的参数会成为暂停的 yield 整个表达式 的值。\n",
    "\n",
    "协程是指一个过程，这个过程与调用方协作，产出由调用方提供的值。下面我们再回到 yield from。\n",
    "\n",
    "前面提到 yield from 主要是用于嵌套的生成器，所以，把它当做协程的时候，它的主要功能是打开双向通道，把最外层的调用方与最内层的子生成器连接起来，这样二者可以直接发送和产出值，还可以直接传入异常，而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构，协程可以通过以前不可能的方式委托职责。\n",
    "\n",
    "# 子生成器\n",
    "def accumulator():\n",
    "    total = 0\n",
    "    while True:\n",
    "        input = yield\n",
    "        if input is None: # 如果调用方传入None，这里就跳出循环，total重新置零\n",
    "            return total # 子生成器可以返回结果给外层生成器middleware\n",
    "        total += input\n",
    "\n",
    "# `yield from`所在的函数相当于一个管道，将调用方client和子生成器accumulator串联起来\n",
    "# 它的职责是负责传递信息以及异常处理，而子生成器就专职做自己该做的事。\n",
    "def middleware(results):\n",
    "    while True:\n",
    "        result = yield from accumulator()\n",
    "        results.append(result)\n",
    "\n",
    "# 调用方（客户端）\n",
    "def client():\n",
    "    results = [] # 用于收集结果\n",
    "    counter = middleware(results)\n",
    "    next(counter) # 激活协程\n",
    "    for i in range(5):\n",
    "        counter.send(i)\n",
    "    counter.send(None) # 关闭当前的生成器对象，协程重置\n",
    "\n",
    "    for i in range(3):\n",
    "        counter.send(i)\n",
    "    counter.send(None)\n",
    "    print(results)\n",
    ">>> client()\n",
    "[10, 3]\n",
    "今天给大家介绍了生成器如何作为协程使用，注意，这里的 yield 出现在表达式的右边。\n",
    "\n",
    "内置函数 iter ()\n",
    "\n",
    "另外，在第 5 小节中，我们提到过iter(obj)返回的是一个迭代器，今天，我们认识一下一种新的iter函数形式iter(func, sentinel)。和之前介绍的iter函数用法不一样的地方在于，参数形式变了，而且你可以去控制迭代。\n",
    "\n",
    "iter(obj) 传进去是一个可迭代对象，而 iter(func, sentinel) 的第一个参数是可调用对象，多数情况下即函数对象，第二个参数是一个哨符，用于指示迭代器去抛出 StopIteration。如果可迭代对象的返回值等于这个哨符，那么迭代器停止。\n",
    "\n",
    ">>> from random import randint\n",
    ">>> def get_number(): # 无参\n",
    "...    return randit(1, 6) # 随机返回一个在1~6范围内的整数\n",
    ">>> iter_num = iter(get_number, 2)  # iter的第一个参数是一个可调用对象\n",
    ">>> for num in iter_num: # 直到get_number()返回2，停止循环\n",
    "...    print(num)\n",
    "问题：魔法函数__call__怎么使用\n",
    "面试官想从这道题中考察面试人什么？\n",
    "使用 Python 的魔术方法，可以以一种简单的方法来让对象可以表现的像内置类型一样。比如如果一个类定义了名为__getitem__() 的方法，并且x为该类的一个实例，则x[i]基本就等同于 type(x).__getitem__(x, i)。也就是说，你需要在自定义的类中实现一些接口，但是你可以只实现部分接口，这样你就可以去对新序列对象访问单个元素，迭代，in运算。同样，你希望你的新类型创建的实例可以像函数对象一样被调用，那么就必须实现__call__。这是魔法方法的最大优势。\n",
    "\n",
    "掌握一些基本的魔法方法，可以让你创建出与其他 Python 特性无缝集成的类，这是非常有必要的。\n",
    "\n",
    "答案\n",
    "魔法函数__call__可以模拟可调用对象，这个方法在实例作为一个函数被 “调用” 时被调用；如果定义了此方法，则 x(arg1, arg2, ...) 就相当于 x.__call__(arg1, arg2, ...) 的快捷方式。\n",
    "\n",
    "拓展\n",
    "魔法方法非常强大，但是一般情况下，我们是不需要直接调用魔法方法，只有在定制的类中，你可以去重写它们。我们看一个简单的例子：\n",
    "\n",
    "class MyClass:\n",
    "    def __call__(self, *args):\n",
    "        print(*args)\n",
    "\n",
    ">>> obj = MyClass()\n",
    ">>> obj(123) # 可以像调用函数一样去调用obj对象\n",
    "对于模拟其它内置类型需要使用到的魔法方法，可以参考官方文档 - 魔法方法，这里就不一一展开了。特别提一下几个非常常用的：\n",
    "\n",
    "__new__\n",
    "__str__ , __repr__\n",
    "__iter__\n",
    "__getitem__ , __setitem__ , __delitem__\n",
    "__getattr__ , __setattr__ , __delattr__\n",
    "__call__\n",
    "小结\n",
    "上一个小节和这一个小节中，我们深入了解了 Python 的一些高级技能：\n",
    "\n",
    "另一个元编程技术，通过getattr，setattr和hasattr去动态控制对象属性\n",
    "如何在 Python 中实现重载\n",
    "加深对生成器和迭代器的认识，继续对第 5 小节关于生成器和迭代器的一个补充：yield构造协程和iter函数的用法\n",
    "可调用对象的本质，即魔法办法__call__"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}